﻿/*  DISCLAIMER ***

    Copyright (c) 2014 Michael T. Joiner

    Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
    to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
    and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
    WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  
 */

#region Using Statements

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using Microsoft.Xna;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;

using FlatRedBall;
using FlatRedBall.Content;
using FlatRedBall.Content.Scene;
using FlatRedBall.Graphics;
using FlatRedBall.Gui;
using FlatRedBall.Screens;

using FRBMouse = FlatRedBall.Input.Mouse;

#endregion

namespace Application.SceneEditor.Controls
{
    /// <summary>
    /// Interaction logic for SceneControl.xaml
    /// </summary>
    public partial class SceneControl : UserControl
    {
        // Event Handler for when this objects' scene has been initialized
        public event EventHandler SceneInitialized;

        #region Properties

        /// <summary>
        /// Gets or Sets a value indicating the amount of multisampling to use for this control
        /// </summary>
        public int Multisamples
        {
            get { return (int)GetValue(MultisamplesProperty); }
            set { SetValue(MultisamplesProperty, value); }
        }

        /// <summary>
        /// Gets or Sets a value indicating whether the render panel should synchronize with vsync
        /// </summary>
        public bool SynchronizeWithVerticalRetrace { get; set; }        

        /// <summary>
        /// Gets a pointer to the handle of the render panel of this control
        /// </summary>
        public IntPtr Handle { get { return RenderPanel.Handle; } }

        #endregion

        #region Methods

        #region Constructor

        /// <summary>
        /// Constructs a static instance of this object
        /// </summary>
        static SceneControl()
        {
            MultisamplesProperty = DependencyProperty.Register("Multisamples", typeof(int), typeof(SceneControl), new PropertyMetadata(0));
        }

        /// <summary>
        /// Constructs a new instance of this object
        /// </summary>
        public SceneControl()
        {
            if (!System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
                this.Loaded += OnLoaded;

            InitializeComponent();
        }

        #endregion

        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            Loaded -= OnLoaded;

            m_XnaGame = new XnaGame(this);
            m_UpdateLength = TimeSpan.FromSeconds(1.0 / 200.0f);
            m_TickAction = () => m_XnaGame.Tick();
            
            Task.Run(() => 
            {
                while (true)
                {
                    Dispatcher.Invoke(m_TickAction, null);
                    Task.Delay(m_UpdateLength).Wait();
                }   
            });

            if (SceneInitialized != null)
                SceneInitialized(this, EventArgs.Empty);
        }

        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            m_XnaGame.Exit();
            m_XnaGame.Dispose();
        }

        #endregion

        private XnaGame m_XnaGame;
        private Action m_TickAction;
        private TimeSpan m_UpdateLength;

        public static readonly DependencyProperty MultisamplesProperty;
    }

    /// <summary>
    /// Class which represents the Game object used by the FRB Game Engine
    /// </summary>
    sealed class XnaGame : Game
    {
        #region Methods

        #region Constructor

        /// <summary>
        /// Constructs a new instance of this object, rendering to the supplied host
        /// </summary>
        /// <param name="controlHost">a host window, to draw the content to</param>
        public XnaGame(SceneControl controlHost)
        {
            this.m_XnaPanel = controlHost;
            this.m_XnaPanel.SizeChanged += OnHostSizeChanged;
            this.m_Graphics = new GraphicsDeviceManager(this);
            this.m_Graphics.PreferMultiSampling = true;
            this.m_Graphics.SynchronizeWithVerticalRetrace = true;
            this.m_Graphics.PreparingDeviceSettings += OnPreparingGraphicsDeviceSettings;
            this.IsFixedTimeStep = false;

            this.m_XnaPanel.MouseMove += OnHostMouseMove;

            this.Initialize();
            this.LoadContent();
        }

        #endregion

        protected override void Initialize()
        {
            var graphicsOptions = new GraphicsOptions(this, m_Graphics);
            graphicsOptions.SuspendDeviceReset();
            graphicsOptions.ResolutionHeight = (int)m_XnaPanel.RenderSize.Height;
            graphicsOptions.ResolutionWidth = (int)m_XnaPanel.RenderSize.Width;
            graphicsOptions.ResumeDeviceReset();
            FlatRedBallServices.InitializeFlatRedBall(this, m_Graphics, graphicsOptions);
            FRBMouse.ModifyMouseState += HandleFRBMouseState;
            
            GuiManager.Cursor.CustomIsActive = () =>
            {
                if (System.Windows.Application.Current.MainWindow != null)
                    return System.Windows.Application.Current.MainWindow.IsActive;

                return false;
            };
            
            base.Initialize();
        }

        protected override void Update(GameTime gameTime)
        {
            FlatRedBallServices.Update(gameTime);
            ScreenManager.Activity();
            
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            FlatRedBallServices.Draw();
            
            base.Draw(gameTime);
        }

        protected override void EndDraw()
        {
            base.EndDraw();

            if (GraphicsDevice != null)
                GraphicsDevice.Present();
        }

        private void OnPreparingGraphicsDeviceSettings(object sender, PreparingDeviceSettingsEventArgs e)
        {
            var pp = e.GraphicsDeviceInformation.PresentationParameters;
            pp.BackBufferWidth = (int)m_XnaPanel.RenderSize.Width;
            pp.BackBufferHeight = (int)m_XnaPanel.RenderSize.Height;
            pp.MultiSampleCount = (int)m_XnaPanel.Multisamples;            
            pp.DeviceWindowHandle = m_XnaPanel.Handle;
            pp.RenderTargetUsage = RenderTargetUsage.PreserveContents;
        }

        private void OnHostSizeChanged(object sender, SizeChangedEventArgs e)
        {
            var width = (int)e.NewSize.Width;
            var height = (int)e.NewSize.Height;

            FlatRedBallServices.GraphicsOptions.SetResolution(width, height);
            SpriteManager.Camera.DestinationRectangle = new Microsoft.Xna.Framework.Rectangle(0, 0, width, height);
        }

        private void OnHostMouseMove(object sender, MouseEventArgs e)
        {
            // Since this is only updated ~60 fps we need to lock
            // the LastMouseState while reading and writing, since the game loop
            // can be at varying speeds

            lock(SyncContext)
            {
                var mousePosition = e.GetPosition(m_XnaPanel);
                m_LastMouseState = new MouseState((int)mousePosition.X, (int)mousePosition.Y, 0, (ButtonState)e.LeftButton, (ButtonState)e.MiddleButton, (ButtonState)e.RightButton, (ButtonState)e.XButton1, (ButtonState)e.XButton2);
            }
        }

        private void HandleFRBMouseState(ref MouseState mouseState)
        {
            // make sure we set using the current values
            lock(SyncContext)
            {
                // copy the last mouse state to the current mouse state
                mouseState = new MouseState
                (
                    m_LastMouseState.X,
                    m_LastMouseState.Y,
                    0,
                    m_LastMouseState.LeftButton,
                    m_LastMouseState.MiddleButton,
                    m_LastMouseState.RightButton,
                    m_LastMouseState.XButton1,
                    m_LastMouseState.XButton2
                );
            }
        }

        #endregion

        private object SyncContext = new object();

        private SceneControl m_XnaPanel;
        
        private MouseState m_LastMouseState;
        private GraphicsDeviceManager m_Graphics;
    }
}
